import java.util.ArrayList;
import java.util.List;

/**
 * The BattleshipModel contains the state of the battleship game. Because a
 * battleship game involves two opposing players each with their own pieces,
 * attacks, and information, the state of the game is stored into two Players.
 * 
 * @convention Limited to being a two player game.
 * @correspondence Represented by two opposing players in a game of battleship.
 * @author Vo, Wu, and Wyan
 * 
 */
public class BattleshipModel {
	/**
	 * The first player to place ships and attack.
	 */
	private Player player1 = new Player();
	/**
	 * The second player to place ships and attack.
	 */
	private Player player2 = new Player();

	/**
	 * The maximum size of the ButtonGrid.
	 */
	private static final int GRID_MAX = 10;

	/**
	 * Depending on if it is player 1's turn, the player to be focused on is
	 * determined.
	 * 
	 * @param p1Turn
	 *            Whether or not it is player 1's turn.
	 * @return Returns the player whose state will be affected.
	 */
	public final Player getPlayer(Boolean p1Turn) {
		if (p1Turn) {
			return player1;
		} else {
			return player2;
		}
	}

	/**
	 * Adds the locations a ship occupies based on the ship index provided to
	 * the corresponding ship for the player. Also adds all the ship cells to
	 * the overall array of cells the player's ships occupy in total.
	 * 
	 * @param player
	 *            The player whose ship spaces are being altered.
	 * @param array
	 *            The array of cells the ship will occupy.
	 * @param shipIndex
	 *            The array index the current ship is located at.
	 * @alters player
	 * @requires shipIndex is within the bounds of player.ships array, an
	 *           integer ranging from 0-4.
	 * @ensures player.all_ships = #player.all_ships + array
	 */
	private void addShipCells(Player player, List<String> array, int shipIndex) {
		for (String cell : array) {
			player.getShips()[shipIndex].addCell(cell);
			player.getShipCells().add(cell);
		}
	}

	/**
	 * Marks a cell as being already used by the player.
	 * 
	 * @param player
	 *            The player whose turn it is.
	 * @param clickedCell
	 *            The cell which has been clicked already.
	 */
	public final void addUsedCell(Player player, String clickedCell) {
		player.addAttackedCell(clickedCell);
	}
	
	/**
	 * The valid endpoints, a maximum of two, for a given ship given its
	 * starting position on the ButtonGrid is returned as an array. The cells
	 * laying vertical the start point are checked for occupancy before the
	 * cells horizontally are checked. If there are no valid points, an empty
	 * array will be returned.
	 * 
	 * @param p1Turn
	 *            Whether or not it is player 1's turn.
	 * @param start
	 *            The starting position of the ship to be placed. This position
	 *            will always be the top-left-most position from which valid
	 *            points are calculated.
	 * @param shipIndex
	 *            The array index the current ship is located at.
	 * @requires shipIndex is within the bounds of player.ships array, an
	 *           integer ranging from 0-4.
	 * @ensures For each String returned, the cell designated is a valid cell
	 *          within the ButtonGrid.
	 * @return An array of valid points the ship's endpoint may be placed at.
	 */
	public final List<String> getEndpoints(Boolean p1Turn, String start,
			int shipIndex) {
		Player player = this.getPlayer(p1Turn);
		List<String> validPoints = new ArrayList<String>();
		int x = Character.getNumericValue(start.charAt(0)) - 9;
		int y = 0;
		if (start.length() > 2 && start.charAt(2) == '0') {
			y = GRID_MAX;
		} else {
			y = Character.getNumericValue(start.charAt(1));
		}
		int len = player.getShips()[shipIndex].getLength();
		// Check the vertical case
		if (!(x + len - 1 > GRID_MAX)) {
			boolean occupied = false;
			for (int i = x + len - 1; i > x; i--) {
				String spot = (char) (i + 64) + Integer.toString(y);
				occupied = player.getShipCells().contains(spot);
				if (occupied) {
					break;
				}
			}
			if (!occupied) {
				validPoints.add(Integer.toString(x + len - 1)
						+ Integer.toString(y));
			}
		}
		// Check the horizontal case
		if (!(y + len - 1 > GRID_MAX)) {
			boolean occupied = false;
			for (int i = y + len - 1; i > y; i--) {
				String spot = (char) (x + 64) + Integer.toString(i);
				occupied = player.getShipCells().contains(spot);
				if (occupied) {
					break;
				}
			}
			if (!occupied) {
				validPoints.add(Integer.toString(x)
						+ Integer.toString(y + len - 1));
			}
		}
		return validPoints; // highlighted by the view
	}

	/**
	 * Places a player's ship given its index in the ship array, a start and end
	 * point. The ship will occupy the cells between the start and end points
	 * inclusive.
	 * 
	 * @param p1Turn
	 *            Whether or not it is player 1's turn.
	 * @param start
	 *            The starting point of the current ship being placed.
	 * @param end
	 *            The ending point of the current ship being placed.
	 * @param shipIndex
	 *            The array index the current ship is located at.
	 * @requires shipIndex is within the bounds of player.ships array, an
	 *           integer ranging from 0-4.
	 */
	public final void placeShip(Boolean p1Turn, String start, String end,
			int shipIndex) {
		Player player = getPlayer(p1Turn);
		char xStart = (char) (start.charAt(0));
		int yStart = Character.getNumericValue(start.charAt(1));
		char xEnd = (char) (end.charAt(0));
		int yEnd = 0;
		if (start.length() > 2) {
			yStart = GRID_MAX;
		} else {
			yStart = Character.getNumericValue(start.charAt(1));
		}
		if (end.length() > 2) {
			yEnd = GRID_MAX;
		} else {
			yEnd = Character.getNumericValue(end.charAt(1));
		}
		List<String> shipCells = new ArrayList<String>();
		// Vertical
		if (yStart == yEnd) {
			for (int i = xStart; i < xEnd + 1; i++) {
				String s = (char) i + Integer.toString(yStart);
				shipCells.add(s);
			}
		}
		// Horizontal
		if (xStart == xEnd) {
			for (int i = yStart; i < yEnd + 1; i++) {
				String s = (char) xStart + Integer.toString(i);
				shipCells.add(s);
			}
		}
		addShipCells(player, shipCells, shipIndex);
	}

	/**
	 * Checks a cell to see if it is currently occupied during ship placement.
	 * 
	 * @param p1Turn
	 *            Whether or not it is player 1's turn.
	 * @param clickedCell
	 *            The cell that is being checked for ship occupation.
	 * @return Returns whether or not the cell clicked contains a ship already.
	 */
	public final Boolean isValidPlacement(Boolean p1Turn, String clickedCell) {
		boolean answer = true;
		Player player = this.getPlayer(p1Turn);
		if (player.getShipCells().contains(clickedCell)) {
			answer = false;
		}
		return answer;
	}
	
	/**
	 * Determines the result of player 1's attack selection. The result will
	 * either be that player 1 has sunk an enemy ship, hit an enemy ship, miss,
	 * or invalid attack.
	 * 
	 * @param cell
	 *            The cell player 1 has selected to attack.
	 * @return The result of player 1's attack.
	 */
	public final String p1Attack(String cell) {
		String result = "";
		if (player1.isValidAttack(cell)) {
			player1.addAttackedCell(cell);
			if (player2.isHit(cell)) {
				if (!getSunkShip(player2, cell).equals("")) {
					result = getSunkShip(player2, cell);
				} else {
					result = "HIT";
				}
			} else {
				result = "MISS";
			}
		} else {
			result = "INVALID";
		}
		return result;
	}

	/**
	 * Determines the result of player 2's attack selection. The result will
	 * either be that player 2 has sunk an enemy ship, hit an enemy ship, miss,
	 * or invalid attack.
	 * 
	 * @param cell
	 *            The cell player 2 has selected to attack.
	 * @return The result of player 2's attack.
	 */
	public final String p2Attack(String cell) {
		String result = "";
		if (player2.isValidAttack(cell)) {
			player2.addAttackedCell(cell);
			if (player1.isHit(cell)) {
				if (!getSunkShip(player1, cell).equals("")) {
					result = getSunkShip(player1, cell);
				} else {
					result = "HIT";
				}
			} else {
				result = "MISS";
			}
		} else {
			result = "INVALID";
		}
		return result;
	}

	/**
	 * Checks a cell to see if it has already been attacked before.
	 * 
	 * @param player
	 *            The player whose turn it is currently.
	 * @param clickedCell
	 *            The cell to be checked if it has been hit before.
	 * @return Whether or not the cell has been hit before.
	 */
	public final Boolean isValidAttack(Player player, String clickedCell) {
		boolean answer = true;
		if (player.getUsedCells().contains(clickedCell)) {
			answer = false;
		}
		return answer;
	}

	/**
	 * Checks if the clicked cell registers a ship hit for the player.
	 * 
	 * @param player
	 *            The player who has been attacked.
	 * @param clickedCell
	 *            The cell which has been attacked.
	 * @return Whether or not the attack has hit a ship.
	 */
	public final Boolean isHit(Player player, String clickedCell) {
		boolean answer = false;
		if (player.isHit(clickedCell)) {
			answer = true;
		}
		return answer;
	}

	/**
	 * Returns the name of ship associated with the player ship array index.
	 * 
	 * @param p1Turn
	 *            Whether or not it is player 1's turn.
	 * @param shipIndex
	 *            The array index the current ship is located at.
	 * @requires shipIndex is within the bounds of player.ships array, an
	 *           integer ranging from 0-4.
	 * @return The name of the current ship.
	 */
	public final String getShipName(Boolean p1Turn, int shipIndex) {
		Player player = this.getPlayer(p1Turn);
		return (player.getShips()[shipIndex].getName());
	}
	
	/**
	 * Retrieves the name of the ship that has been sunk, if one has been sunk
	 * at all.
	 * 
	 * @param player
	 *            The player whose ships are checked for being sunk.
	 * @param clickedCell
	 *            The cell that must belong to the ship that has been sunk.
	 * @return The name of the ship that has just been sunk, otherwise return
	 *         empty string.
	 */
	public final String getSunkShip(Player player, String clickedCell) {
		String ship = "";
		for (Ship s : player.getShips()) {
			if (s.getCells().contains(clickedCell)) {
				if (s.isSunk()) {
					ship = s.getName();
				}
			}
		}
		return ship;
	}
	
	/**
	 * Retrieves an array of all the cells a just sunk ship occupies.
	 * 
	 * @param p1Turn
	 *            Whether or not it is player 1's turn.
	 * @param name
	 *            The name of the ship that has been sunk.
	 * @requires The name must be a valid ship name of the five different ship
	 *           types.
	 * @return Array of cell locations the sunken ship had occupied.
	 */
	public final List<String> getSunkCells(Boolean p1Turn, String name) {
		List<String> sunkCells = new ArrayList<String>();
		Player player = this.getPlayer(!p1Turn);
		for (Ship s : player.getShips()) {
			if (s.getName().equals(name)) {
				sunkCells = s.getCells();
			}
		}
		return sunkCells;
	}

	/**
	 * Retrieves an array of all the cells of a player that have not been hit.
	 * 
	 * @param p1Turn
	 *            Whether or not it is player 1's turn.
	 * @return Array of cell locations which a ship occupies that has not been
	 *         hit.
	 */
	public final List<String> getSurvivingCells(Boolean p1Turn) {
		Player player = this.getPlayer(p1Turn);
		return player.getShipCells();
	}
	
	/**
	 * Checks whether a player is defeated or not, signaling game over.
	 * 
	 * @param p1Turn
	 *            Whether or not it is player 1's turn.
	 * @return True if either player is shown as defeated with no more ships
	 *         left. False otherwise.
	 */
	public final boolean isGameOver(Boolean p1Turn) {
		boolean answer = false;
		if (p1Turn) {
			if (player2.isDefeated()) {
				answer = true;
			}
		} else {
			if (player1.isDefeated()) {
				answer = true;
			}
		}
		return answer;
	}
}
